/*
 * TP0 Organizacion de Computadoras 66.20
 * Práctica: Santi - Perez Masci
 * Integrantes:
 * 				Pereira, Fernando Roque 91610
 *				Montoya, Diego Ramiro 91939
 *				Aguila Cudicio, Barbara 92071
 *
 */

#define ERROR_COMANDOS 1
#define ERROR_INPUT_FILE_NOT_CREATED 2
#define ERROR_OUTPUT_FILE_NOT_CREATED 3
#define ERROR_CARACTER_INVALIDO 4
#define ERROR_OPERACION_ARCHIVO 5
#define ERROR_DELIM_INVALIDO 6
#define ERROR_LECTURA_ESCRITURA_ARCHIVO 7

#define MSG_HELP "Usage:\
					\n tp0 -h\
					\n tp0 -V\
					\n tp0 [options]\
				\n\n Options:\
					\n -h, --help		Print this information.\
					\n -V, --version	Print version and quit.\
					\n -O, --octal		Octal dump mode: encode the input (binary file) in octal format (default).\
					\n -b, --binary		Binary mode: decode the input (octal dump file) in binary format.\
					\n -i, --input		Path to input file (defaults to stdin).\
					\n -o, --output		Path to output file (defaults to stdout)).\
					\n -l, --length		Number of octal bytes per line (line length) at the output in octal dump mode (0 = default = no limit).\
					\n -d, --delimiter	Delimiter between octal bytes. Could be a string, not only a single byte. Used as delimiter for octal output and input files (default: none).\n\n"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <math.h>

int codificar(FILE* inputFile, FILE* outputFile, int length, char* delim);
int decodificar(FILE* inputFile, FILE* outputFile, int length, char* delim);
int comprobarErrorArchivo (FILE* archivo);

int main (int argc, char* argv[]){

	int c = 0, retorno = 0;
	unsigned long max_bytes = ULONG_MAX;
	int octal = 1; //Flag para ver el modo de encoding
	char* delim = "";

	//Estructura que se pasa por parámetro a función 'getopt_long'
	static struct option long_commands[] = {
	                   {"help",0,NULL,'h'},
	                   {"version",0,NULL,'V'},
	                   {"octal",0, NULL,'O'},
	                   {"binary",0,NULL,'b'},
	                   {"input",1,NULL,'i'},
	                   {"output",1,NULL,'o'},
	                   {"length",1,NULL,'l'},
	                   {"delimiter",1,NULL,'d'},
	                   {0,0,0,0}
	               };

	opterr = 0; //Se indica 0 para que no imprima directamente mensaje de error

	FILE* inputFile = stdin;
	FILE* outputFile = stdout;

	/*	getopt_long: 	Devuelve los parametros en el int c. Si no hay mas parametros,
	 * 					devuelve -1.
	 * 		char* optarg - Nos da el argumento a analizar.
	*/
	while (((c = getopt_long(argc,argv,"hVObi:o:l:d:",long_commands,NULL)) != -1)){
		switch (c){
			case 'h': 	printf(MSG_HELP);
						return 0;
						break;

			case 'V': 	printf("\nVersión 1.0 \n\n");
						return 0;
						break;

			case 'O': 	octal = 1;
						break;

			case 'b': 	octal = 0;
						break;

			case 'i': 	if (strcmp(optarg,"-") != 0){
							inputFile = fopen(optarg,"rb");
							if (inputFile == NULL){
								fprintf(stderr, "Archivo de entrada no generado \n\n");
								return (ERROR_INPUT_FILE_NOT_CREATED);
							}
						}
						break;

			case 'o': 	if (strcmp(optarg,"-") != 0){
							outputFile = fopen(optarg,"wb");
							if (outputFile == NULL){
								fprintf(stderr, "Archivo de salida no generado \n\n");
								return ERROR_OUTPUT_FILE_NOT_CREATED;
							}
						}
						break;

			case 'l': 	max_bytes = atoi(optarg); //Indica cantidad maxima de bytes a imprimir
						break;

			case 'd': 	delim = (char*) malloc (strlen(optarg) +1);
						strcpy(delim,optarg); //Indica delimitador a utilizar
						break;

			case '?': 	fprintf(stderr,"Parámetro mal escrito o sin argumento necesario. "
										"Utilice -h para ver modo de uso y opciones\n\n");
						return ERROR_COMANDOS;
						break;

			default:	break;
		}
	}

	if (octal) retorno = codificar(inputFile, outputFile, max_bytes, delim);
	else retorno = decodificar(inputFile, outputFile, max_bytes, delim);

	if (strcmp(delim,"") != 0) free(delim);

	fclose(inputFile);
	fclose(outputFile);

	return retorno;
}

int comprobarErrorArchivo (FILE* archivo){
	if (ferror(archivo)){
		fprintf(stderr, "Error al intentar leer/escribir un archivo\n");
		return 1;
	}
	return 0;
}

int caracteresNoIguales (char a, char b){
	if (a != b){
		fprintf(stderr,"El delimitador no es el correcto\n\n");
		return ERROR_DELIM_INVALIDO;
	}
	return 0;
}

int codificar(FILE* inputFile, FILE* outputFile, int max_bytes, char* delim){
	int c,counter = 0,cant_bytes = 3;

	//Impresion a octal
	c = fgetc(inputFile);
	if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
	if (c != EOF){
		while (!(feof(inputFile))) {
			if (max_bytes == counter){
				fprintf(outputFile,"\n");
				if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
				counter = 0;
			} else if (counter != 0){
				fprintf(outputFile,"%s",delim);
				if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
			}
			fprintf(outputFile,"%.*o",cant_bytes,c);
			if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
			counter = counter + 1;
			c = fgetc(inputFile);
			if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
		}
	}
	return 0;
}

int isNotOctalDigit(int num){
	if ((num < 48) || (num > 55)){ //Se comprueba su valor ASCII
		fprintf(stderr,"El caracter es invalido\n\n");
		return ERROR_CARACTER_INVALIDO;
	}
	return 0;
}

int isNotValidChar(int num){
	if ((num < 0) || (num > 255)){
		fprintf(stderr,"El caracter es invalido\n\n");
		return ERROR_CARACTER_INVALIDO;
	}
	return 0;
}

/*Funcion para pasar un entero en base octal pasado por parametro a base decimal*/
int octalADecimal(int octal)
{
	int valor = 0, digito, decimal=0;
	while (octal > 0)
	{
		digito = octal%10;
		octal /= 10;
		decimal += digito * (int) pow (8,valor);
		valor++;
	}
	return decimal;
}

int decodificar(FILE* inputFile, FILE* outputFile, int max_bytes, char* delim){
	int i, limite = 3;
	char oct[3];
	int num, dec;
	while (!(feof(inputFile))){
		for (i=0; i < limite; i++){
			num = fgetc(inputFile);
			if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
			if (num == '\n') break; //Para que no trate el caracter de salto de linea
			if (num != EOF && num!= '\n'){
				if (isNotOctalDigit(num)) return ERROR_CARACTER_INVALIDO;
				oct[i] = (char) num;
			} else return 0; //Para que no imprima dos veces el caracter final
		}

		if (num == '\n') continue; //Para que no trate el caracter de salto de linea
		num = atoi(oct);
		dec = octalADecimal(num);

		//Comprueba que el caracter a imprimir pertenezca a ASCII
		if (isNotValidChar(dec)) return ERROR_CARACTER_INVALIDO;

		fprintf(outputFile, "%c", dec);
		if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;

		//Si hay delimitador, leo para saber si tengo que leer delimitador o seguir (salto de linea)
		if (strcmp(delim,"") != 0){
			num = fgetc(inputFile);
			if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
			if ((num != '\n') && (num != EOF)){
				if (caracteresNoIguales(num,delim[0])) return ERROR_DELIM_INVALIDO;
				//Debe leer el delimitador
				for (i = 1; i < strlen(delim); i++){
					num = fgetc(inputFile);
					if (comprobarErrorArchivo(outputFile)) return ERROR_LECTURA_ESCRITURA_ARCHIVO;
					if (caracteresNoIguales(num,delim[i])) return ERROR_DELIM_INVALIDO;
				}
			}
		}
	}
	return 0;
}
